package com.puff.framework.threadlocal;

import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 利用对象继承的内存布局规则来padding避免false sharing, 注意其中对象头会至少占用8个字节
 * ---------------------------------------
 *  For 32 bit JVM:
 *      _mark   : 4 byte constant
 *      _klass  : 4 byte pointer to class
 *  For 64 bit JVM:
 *      _mark   : 8 byte constant
 *      _klass  : 8 byte pointer to class
 *  For 64 bit JVM with compressed-oops:
 *      _mark   : 8 byte constant
 *      _klass  : 4 byte pointer to class
 * ---------------------------------------
 */
class LhsPadding {
	protected long p01, p02, p03, p04, p05, p06, p07;
}

class Fields extends LhsPadding {
	Object[] indexedVariables;

	// string-related thread-locals
	StringBuilder stringBuilder;
}

class RhsPadding extends Fields {
	protected long p09, p10, p11, p12, p13, p14, p15;
}

/**
 * 参考了 <a href="https://github.com/netty/netty">Netty</a> FastThreadLocal 的设计
 *
 */
public final class FastThreadLocalMap extends RhsPadding {

	public static final Object UNSET = new Object();

	private static final ThreadLocal<FastThreadLocalMap> slowThreadLocalMap = new ThreadLocal<>();
	private static final AtomicInteger nextIndex = new AtomicInteger();

	public static FastThreadLocalMap getIfSet() {
		Thread thread = Thread.currentThread();
		if (thread instanceof FastThread) {
			return ((FastThread) thread).threadLocalMap();
		}
		return slowThreadLocalMap.get();
	}

	public static FastThreadLocalMap get() {
		Thread thread = Thread.currentThread();
		if (thread instanceof FastThread) {
			return fastGet((FastThread) thread);
		}
		return slowGet();
	}

	public static void remove() {
		Thread thread = Thread.currentThread();
		if (thread instanceof FastThread) {
			((FastThread) thread).setThreadLocalMap(null);
		} else {
			slowThreadLocalMap.remove();
		}
	}

	public static void destroy() {
		slowThreadLocalMap.remove();
	}

	public static int nextVariableIndex() {
		int index = nextIndex.getAndIncrement();
		if (index < 0) {
			nextIndex.decrementAndGet();
			throw new IllegalStateException("too many thread-local indexed variables");
		}
		return index;
	}

	public static int lastVariableIndex() {
		return nextIndex.get() - 1;
	}

	private FastThreadLocalMap() {
		indexedVariables = newIndexedVariableTable();
	}

	public Object indexedVariable(int index) {
		Object[] lookup = indexedVariables;
		return index < lookup.length ? lookup[index] : UNSET;
	}

	/**
	 * @return {@code true} if and only if a new thread-local variable has been created
	 */
	public boolean setIndexedVariable(int index, Object value) {
		Object[] lookup = indexedVariables;
		if (index < lookup.length) {
			Object oldValue = lookup[index];
			lookup[index] = value;
			return oldValue == UNSET;
		} else {
			expandIndexedVariableTableAndSet(index, value);
			return true;
		}
	}

	public Object removeIndexedVariable(int index) {
		Object[] lookup = indexedVariables;
		if (index < lookup.length) {
			Object v = lookup[index];
			lookup[index] = UNSET;
			return v;
		} else {
			return UNSET;
		}
	}

	public int size() {
		int count = 0;
		for (Object o : indexedVariables) {
			if (o != UNSET) {
				++count;
			}
		}
		return count;
	}

	public StringBuilder stringBuilder() {
		StringBuilder builder = stringBuilder;
		if (builder == null
				|| builder.capacity() > (1024 << 6) /* ensure memory overhead */ ) {
			stringBuilder = builder = new StringBuilder(512);
		} else {
			builder.setLength(0);
		}
		return builder;
	}

	private static Object[] newIndexedVariableTable() {
		Object[] array = new Object[32];
		Arrays.fill(array, UNSET);
		return array;
	}

	private static FastThreadLocalMap fastGet(FastThread thread) {
		FastThreadLocalMap threadLocalMap = thread.threadLocalMap();
		if (threadLocalMap == null) {
			thread.setThreadLocalMap(threadLocalMap = new FastThreadLocalMap());
		}
		return threadLocalMap;
	}

	private static FastThreadLocalMap slowGet() {
		ThreadLocal<FastThreadLocalMap> slowThreadLocalMap = FastThreadLocalMap.slowThreadLocalMap;
		FastThreadLocalMap ret = slowThreadLocalMap.get();
		if (ret == null) {
			ret = new FastThreadLocalMap();
			slowThreadLocalMap.set(ret);
		}
		return ret;
	}

	private void expandIndexedVariableTableAndSet(int index, Object value) {
		Object[] oldArray = indexedVariables;
		final int oldCapacity = oldArray.length;
		int newCapacity = index;
		newCapacity |= newCapacity >>> 1;
		newCapacity |= newCapacity >>> 2;
		newCapacity |= newCapacity >>> 4;
		newCapacity |= newCapacity >>> 8;
		newCapacity |= newCapacity >>> 16;
		newCapacity++;

		Object[] newArray = Arrays.copyOf(oldArray, newCapacity);
		Arrays.fill(newArray, oldCapacity, newArray.length, UNSET);
		newArray[index] = value;
		indexedVariables = newArray;
	}

}
